#include <string.h>
#include "stack.h"
#include "cmds.h"

const double GARBAGE_IN = -666.666;
const double GARBAGE_OUT = -999.999;
const int PROG_SZ = 64;

typedef struct CPU {
	stk *Stack;
	stk *Retstk;
	double ax;
	double bx;
	double cx;
	double dx;
	int *program;
} CPU;

void CPU_construct( CPU* tobuild )
{
	( tobuild -> ax ) = ( tobuild -> bx ) = ( tobuild -> cx ) = ( tobuild -> dx ) = GARBAGE_IN;

	tobuild -> program = ( int* ) calloc( PROG_SZ, sizeof( int ) );
	tobuild -> Stack = ( stk* ) calloc( 1, sizeof( stk ) );
	tobuild -> Retstk = ( stk* ) calloc( 1, sizeof( stk ) );

	stk_construct( ( tobuild -> Stack ) );
	stk_construct( ( tobuild -> Retstk ) );
}

void CPU_destruct( CPU* in )
{
	in -> ax = in -> bx = in -> cx = in -> dx = GARBAGE_OUT;
	free( in -> program );

	stk_destruct( in -> Stack );
	free( in -> Stack );

	stk_destruct( in -> Retstk );
	free( in -> Retstk );
}

void CPU_Load( CPU& proc, const char file[] )
{
	assert( ( "\nCPU_Load has received a valid filename" , file != NULL ) );
	freopen( file, "r", stdin );

	//!< Need to rearrange some constants
	for( int chk = 0, cur_in = POISON, i = 0; i < PROG_SZ; i++ ) {
		chk = scanf( " %d", &cur_in );
		assert( ( "\nCPU_Load hasn't met a single non-digit character in program code" , chk != 0 ) );

		if( chk == -1 )
			break;
		else
			proc.program[i] = cur_in;

		/*
		if(cur_in == CMD_END) {
			DOUT("CPU loaded OK");
			break;
		} else if(chk == -1) {
			DPUT("Last command entered: %d before \n", proc.program[i-1]);
			assert(!"FATAL_ERR: CPU_Load has reached the EOF before encountering END command");
			//!<To do: find a way to force-close a program in release mode
			break;
		}
		*/ //!< Commented out because of possible nonlinearity presented by CALL-RET commands
	}
}

void CPU_Run( CPU& act_proc )
{
	for( int i = 0, breaker = 0; !breaker; i++ ) {
		switch( act_proc.program[i] ) {

			case( CMD_NOP ):
				break;

			case( CMD_CALL ): {
					if( stk_push( act_proc.Retstk, i ) )
						DPUT( "Pushed %d to return stack", i );
					else
						DOUT( "NOTHING WAS PUSHED TO RETURN STACK" );

					i = act_proc.program[i + 1];
					DPUT( "Call-jumped to %d", i + 1 );
					break;
				}

			case( CMD_RET ): {
					stk_d ret_arg = POISON;

					if( stk_pop( act_proc.Retstk, &ret_arg ) )
						DPUT( "Got %d from return stack", ( int )ret_arg );
					else
						DOUT( "NOTHING POPPED FROM RETURN STACK" );

					DPUT( "Returned to %d", ( int )ret_arg + 1 );

					i = ( int )ret_arg + 1;
					break;
				}

			case( CMD_JMP ): {
					i = act_proc.program[i + 1];
					DPUT( "Uncond jump to %d", i );
					break;
				}

			case( CMD_JE ): {
					DPUT( "Called JE with %ld elems", act_proc.Stack -> num );
					stk_d a = POISON, b = POISON;
					stk_pop( act_proc.Stack, &a );
					stk_pop( act_proc.Stack, &b );

					if( a == b ) {
						i = act_proc.program[i + 1];
						DPUT( "Equality jump to %d", i );
						DPUT( "Next command is %d", act_proc.program[i + 1] );
					} else
						i += 1;

					break;
				}

			case( CMD_PUSH ): {
					stk_d arg = act_proc.program[++i];

					if( stk_push( act_proc.Stack, arg ) )
						DPUT( "pushed %lg", arg );
					else
						DOUT( "NOTHING PUSHED!" );

					break;
				}

			case( CMD_PUSHREG ): {
					int arg_reg = act_proc.program[++i];

					switch( arg_reg ) {
#define SET_REG_(nam, lwn)						\
case(REG_##nam):								\
	stk_push(act_proc.Stack, act_proc. lwn);	\
	break

							SET_REG_( AX, ax );
							SET_REG_( BX, bx );
							SET_REG_( CX, cx );
							SET_REG_( DX, dx );

#undef SET_REG_
					};

					break;
				}

			case( CMD_POP ): {
					stk_d dbgval = 0;

					if( stk_pop( act_proc.Stack, &dbgval ) == 1 )
						DPUT( "popped %lg", dbgval );
					else
						DOUT( "NOTHING POPPED!" );

					break;
				}

			case( CMD_POPREG ): {
					int arg_reg = act_proc.program[++i];
					DPUT( "POPREG called with arg %d", arg_reg );

					switch( arg_reg ) {
#define SET_REG_(nam, lwn)						\
case(REG_##nam):								\
	stk_pop(act_proc.Stack, &(act_proc. lwn));	\
	break

							SET_REG_( AX, ax );
							SET_REG_( BX, bx );
							SET_REG_( CX, cx );
							SET_REG_( DX, dx );

#undef SET_REG_
					};

					break;
				}

			case( CMD_MOV ): {
					int arg_reg = act_proc.program[++i];
					stk_d arg = act_proc.program[++i];
					DPUT( "MOV, arg_reg: %d", arg_reg );
					DPUT( "MOV, argum: %lg", arg );

					switch( arg_reg ) {
#define SET_REG_(name,lwn)\
case(REG_##name):		\
	act_proc.lwn = arg;	\
	break

							SET_REG_( AX, ax );
							SET_REG_( BX, bx );
							SET_REG_( CX, cx );
							SET_REG_( DX, dx );

#undef SET_REG_
					};

					break;
				}

			case( CMD_MOVREG ): {
					int first_arg_reg = act_proc.program[++i],
					    secnd_arg_reg = act_proc.program[++i];
					DPUT( "MOVREG, arg1: %d", first_arg_reg );
					DPUT( "MOVREG, arg2: %d", secnd_arg_reg );

#define SET_REG_(nam, low, ident)	\
case(REG_##nam):				\
	switch(secnd_arg_reg) {		\
		case(REG_AX):			\
			act_proc.low = act_proc. ax;\
			break;				\
		case(REG_BX):			\
			act_proc.low = act_proc. bx;\
			break;				\
		case(REG_CX):			\
			act_proc.low = act_proc. cx;\
			break;				\
		case(REG_DX):			\
			act_proc.low = act_proc. dx;\
			break;				\
	};							\
	break

					switch( first_arg_reg ) {
							SET_REG_( AX, ax, first );
							SET_REG_( BX, bx, first );
							SET_REG_( CX, cx, first );
							SET_REG_( DX, dx, first );
					};

					break;
				}

#define SET_CMD_(name)\
case(CMD_##name):								\
	stk_##name(act_proc.Stack);					\
	DPUT("%ld elems remain after STK_" #name " called", act_proc.Stack -> num);	\
	break

				SET_CMD_( SUM );
				SET_CMD_( SUB );
				SET_CMD_( MUL );
				SET_CMD_( DIV );
				SET_CMD_( POW );
				SET_CMD_( EXP );

#undef SET_CMD_

			case( CMD_IN ): {
					DOUT( "Awaiting input:" );
					scanf( " %lg ", &( act_proc.ax ) );
					break;
				}

			case( CMD_OUT ): {
					stk_d out = GARBAGE_OUT;
					stk_pop( act_proc.Stack, &out );
					printf( "\n%lg", out );
					break;
				}

			case( CMD_END ): {
					breaker = 1;
					break;
				}

			default:
				printf( "\n" );
				assert( !"Invalid command received" );
				break;
		}
	}
}
